/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.monitor.log;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.monitor.annotation.MonitorMethod;
import cn.star.framework.monitor.MonitorContext;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import javax.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 日志<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
@ToString
public class Logger implements Serializable {
  /** 主键 */
  @Getter @Setter private String id;

  /** 用户 */
  @Getter @Setter private String username;

  /** 模块 */
  @Getter @Setter private String module;

  /** 操作 */
  @Getter @Setter private String operationType;

  /**
   * 描述<br>
   * 支持模板渲染，在{@link IWriteLogger#write(Logger)}中进行实现即可，框架不支持
   */
  @Getter @Setter private String description;

  /** 用户使用客户端 */
  @Getter @Setter private String userAgent;

  /** 创建时间 */
  @Getter @Setter private Timestamp createTime;

  /** 耗时 */
  @Getter @Setter private Long duration;

  /** 请求Ip */
  @Getter @Setter private String ip;

  /**
   * 请求地址<br>
   * Class上面的{@link RequestMapping#value()}
   */
  @Getter @Setter private String[] requestClassUrls;

  /**
   * 请求方法<br>
   * Class上面的{@link RequestMapping#method()}
   */
  @Getter @Setter private RequestMethod[] requestClassMethods;

  /** 请求地址 */
  @Getter @Setter private String[] requestUrls;

  /** 请求方法 */
  @Getter @Setter private RequestMethod[] requestMethods;

  /** 主方法 */
  @Getter @Setter private String methodName;

  /** 主方法参数 */
  @Getter @Setter private Object methodParameter;

  /** 主方法执行结果 */
  @Getter @Setter private Object methodResult;

  /** 发生错误而不能记录的日志 */
  @Getter @Setter private Throwable error;

  public static Logger to(MonitorContext context) {
    Logger logger = new Logger();
    try {
      MonitorMethod monitor = context.getMonitor();
      logger.setId(IdUtil.simpleUUID());
      logger.setUsername("__ASPECT_AUTO__");
      logger.setModule(monitor.module());
      logger.setOperationType(monitor.operationType());
      logger.setDescription(monitor.description());
      logger.setCreateTime(new Timestamp(System.currentTimeMillis()));
      logger.setDuration(context.getDurationInMillis());
      // web attribute
      setIpAndUserAgent(logger);
      setRequestUrlAndRequestMethod(context, logger);
      // base
      logger.setMethodName(context.getMethodName());
      logger.setMethodParameter(context.getMethodParameter());
      logger.setMethodResult(context.getMethodResult());
    } catch (Exception e) {
      logger.setError(e);
    }

    return logger;
  }

  private static void setIpAndUserAgent(Logger logger) {
    try {
      HttpServletRequest request =
          ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
      String[] names = {"X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP"};
      String ip = request.getRemoteAddr();
      for (String key : names) {
        String value = request.getHeader(key);
        if (StrUtil.isNotBlank(value) && !"unknown".equalsIgnoreCase(value)) {
          ip = value;
          break;
        }
      }

      logger.setIp(ip);
      logger.setUserAgent(request.getHeader("User-Agent"));
    } catch (Exception e) {
      log.warn("get client ip & userAgent error, {}", e.getMessage());
    }
  }

  @SuppressWarnings("all")
  private static void setRequestUrlAndRequestMethod(MonitorContext context, Logger logger) {
    Method method = context.getMethod();
    if (method == null) {
      return;
    }
    Class<?>[] types = {
      RequestMapping.class,
      // 其他情况
      GetMapping.class,
      PostMapping.class,
      PutMapping.class,
      DeleteMapping.class,
      PatchMapping.class
    };
    for (Class type : types) {
      Object restful = method.getAnnotation(type);
      if (restful != null && type.isInstance(restful)) {
        if (RequestMapping.class.isInstance(restful)) {
          RequestMapping m = (RequestMapping) restful;
          logger.setRequestUrls(m.value());
          logger.setRequestMethods(m.method());
        } else {
          RequestMapping m = (RequestMapping) type.getAnnotation(RequestMapping.class);
          logger.setRequestUrls(ReflectUtil.invoke(restful, "value"));
          logger.setRequestMethods(m.method());
        }
        break;
      }
    }
    try {
      RequestMapping m = method.getDeclaringClass().getAnnotation(RequestMapping.class);
      logger.setRequestClassUrls(m.value());
      logger.setRequestClassMethods(m.method());
    } catch (Exception e) {
      if (log.isDebugEnabled()) {
        log.debug("get method declaring class RequestMapping error, {}", e.getMessage());
      }
    }
  }
}
